/*
使用libpmemobj库实现一个可持久化的队列，《持久内存：架构与工程实践》P138
g++ pmem_queue.cpp -lpmem -lpmemobj
./a.out dst_file enque 1
./a.out dst_file show
*/
#include <libpmemobj.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>

#define CREATE_MODE_RW (S_IWRITE | S_IREAD)


/*
持久内存池的布局接口：总是以POBJ_LAYOUT_BEGIN开始，以POBJ_LAYOUT_END结束，并在这俩句中间列出
根对象和所有通过根对象能够访问到的对象。其中根对象为POBJ_LAYOUT_ROOT,其他对象为POBJ_LAYOUT_TOID.
POBJ_LAYOUT_NAME是一个字符串常量。
*/
POBJ_LAYOUT_BEGIN(pmem_que);
POBJ_LAYOUT_ROOT(pmem_que, struct queue);
POBJ_LAYOUT_TOID(pmem_que, struct queue_node);
POBJ_LAYOUT_END(pmem_que);

static int file_exists(char const *file) {
    return access(file, F_OK);
}

//定义了持久内存的三个操作：插入新节点，删除一个节点，显示队列中所有的节点
typedef enum que_op{
    ENQUE,
    DEQUE,
    SHOW,
    MAX_OPS,
} que_op_e;

//定义struct queue_node
struct queue_node {
    int value;
    TOID(struct queue_node) next;
};

//定义根节点struct queue
struct queue {
    TOID(struct queue_node) head;
    TOID(struct queue_node) tail;
};

//将用户输入的操作参数字符串转换成为操作枚举变量，方便枚举操作
const char * ops_str[MAX_OPS] = {"enque", "deque", "show"};
que_op_e parse_que_ops(char * ops)
{
    int i;
    for (i = 0; i < MAX_OPS; i++) {
        if (strcmp(ops_str[i], ops) == 0) {
            return (que_op_e)i;
        }
    }
    return MAX_OPS;
}

//定义了持久内存中的全局根变量
TOID(struct queue) pmem_queue;

//定义了持久化队列的插入操作，是事务操作，使用TX_BEGIN,TX_END
void queue_enque(PMEMobjpool *pop, int value) {
    TX_BEGIN(pop) {
        TOID(struct queue_node) head = D_RW(pmem_queue)->head;
        TX_ADD(pmem_queue);
        TOID(struct queue_node) newnode = TX_NEW(struct queue_node);
        D_RW(newnode)->value = value;
        D_RW(newnode)->next = TOID_NULL(struct queue_node);

        if(TOID_IS_NULL(head)) {
            //持久队列中没有任何节点，根节点的头尾指针都指向新的节点
            D_RW(pmem_queue) -> head = newnode;
            D_RW(pmem_queue) -> tail = newnode;
        } else {
            //持久化队列中已经有了节点，新的节点将插入队列的尾部
            D_RW(D_RW(pmem_queue) -> tail) -> next = newnode;
            D_RW(pmem_queue) -> tail = newnode;
        }

    }TX_END

}

//持久化队列的出列操作，
int queue_deque(PMEMobjpool *pop, int * value) {
    TX_BEGIN(pop) {
        TOID (struct queue_node) head = D_RW(pmem_queue)->head;
        TOID (struct queue_node) tail = D_RW(pmem_queue)->tail;

        //保存在持久化的redo日志中，
        TX_ADD(pmem_queue);
        if (TOID_IS_NULL(head)) {
            return -1;
        } else {
            *value = D_RO(head)->value;
            D_RW(pmem_queue)->head = D_RO(head)->next;
            if(TOID_EQUALS(head, tail)) {
                D_RW(pmem_queue)->tail = D_RO(tail)->next;
                TX_FREE(head);
            }
        }
    }TX_END
    return 0;
}

//遍历持久化的队列的所有的节点，读操作，不是事务的
int queue_show() {
    TOID(struct queue_node) head = D_RO(pmem_queue)->head;
    if (TOID_IS_NULL(head)) return -1;
    while (!TOID_IS_NULL(head)) {
        std::cout << D_RO(head)->value << std::endl;
        head = D_RO(head)->next; 
    }
    return 0;

}





int main(int argc, char * argv[]) {
    if (argc < 3) {
        std::cerr << "usage: " << argv[0] << "file-name......." << std::endl;
        exit(0);
    }

    const char * path = argv[1];
    int sds_write_value = 0;
    pmemobj_ctl_set(NULL, "sds.at_create", &sds_write_value);

    PMEMobjpool *pop;
    if (file_exists(path) != 0) {
        pop = pmemobj_create(path, POBJ_LAYOUT_NAME(pmem_que), PMEMOBJ_MIN_POOL, CREATE_MODE_RW);
    } else {
        pop = pmemobj_open(path, POBJ_LAYOUT_NAME(pmem_que));
    }

    //获取持久队列的根节点
    pmem_queue = POBJ_ROOT(pop, struct queue);

    que_op_e ops = parse_que_ops(argv[2]);
    switch(ops) {
        case ENQUE:
            queue_enque(pop, atoi(argv[3]));
            break;
        case DEQUE:
            int val;
            if (queue_deque(pop, &val) != -1) {
                std::cout << "deque: " << val << std::endl;
            }
            break;
        case SHOW:
            queue_show();
            break;

        case MAX_OPS:
            std::cerr << "unknown ops" << std::endl;
            break;
    }
    pmemobj_close(pop);

    return 0;
}